`timescale 1ns / 1ps

`include "MIPSCPU_COMMON.vh"

module CPUMain(
	rst,
	clk_100M,
	clk_25M,
	cpu_enable,
	instr,
	out_pc_value,
	out_reg_zero_value,
	out_reg_t0_value,
	out_reg_t1_value,
	out_reg_t2_value,
	out_reg_t3_value,
	out_reg_ra_value
    );

	input rst, clk_100M, clk_25M,  cpu_enable;
	input[`DATA_WIDTH - 1 : 0] instr;
	output wire[`DATA_WIDTH - 1 : 0] out_pc_value;
	output wire[`DATA_WIDTH - 1 : 0] out_reg_zero_value, out_reg_t0_value, out_reg_t1_value, out_reg_t2_value, out_reg_t3_value, out_reg_ra_value;
	
	wire clk_fast, clk;
	assign clk_fast = clk_100M;
	assign clk = clk_25M;
	
	wire[`REGISTER_ADDRESS_WIDTH - 1 : 0] reg_read1_addr, reg_read2_addr;
	wire[`DATA_WIDTH - 1 : 0] reg_read1_value, reg_read2_value;
	reg reg_write_enable;
	reg[`REGISTER_ADDRESS_WIDTH - 1 : 0] reg_write_addr;
	reg[`DATA_WIDTH - 1 : 0] reg_written_value;
	
	RegisterFile register_file_i
	(
		.rst(rst),
		.clk(clk),
		.read1_addr(reg_read1_addr),
		.value1(reg_read1_value),
		.read2_addr(reg_read2_addr),
		.value2(reg_read2_value),
		.write_enable(reg_write_enable),
		.write_addr(reg_write_addr),
		.written_value(reg_written_value),
		.zero_value(out_reg_zero_value),
		.t0_value(out_reg_t0_value),
		.t1_value(out_reg_t1_value),
		.t2_value(out_reg_t2_value),
		.t3_value(out_reg_t3_value),
		.ra_value(out_reg_ra_value)
	);
	
	wire[`DATA_WIDTH - 1 : 0] pc_value, hi_value, lo_value;
	reg pc_write_enable, hi_write_enable, lo_write_enable;
	reg[`DATA_WIDTH - 1 : 0] pc_written_value, hi_written_value, lo_written_value;
	
	SpecialRegister special_register_i
	(
		.rst(rst),
		.clk(clk),
		.pc_write_enable(pc_write_enable),
		.pc_written_value(pc_written_value),
		.hi_write_enable(hi_write_enable),
		.hi_written_value(hi_written_value),
		.lo_write_enable(lo_write_enable),
		.lo_written_value(lo_written_value),
		.pc_value(pc_value),
		.hi_value(hi_value),
		.lo_value(lo_value)
	);
	
	wire jump_enable;
	wire[`DATA_WIDTH - 1 : 0] jump_to_addr, if_in_pc_value;
	assign if_in_pc_value = (jump_enable ? jump_to_addr : pc_value);
	
	reg if_enable;
	wire if_out_enable;
	reg tmp_if_out_enable;
	
	InstructionFetch instruction_fetch_i
	(
		.if_enable(if_enable),
		.pc_value(if_in_pc_value),
		.if_out_enable(if_out_enable),
		.out_pc_value(out_pc_value)
	);
	
	reg id_enable;
	reg[`DATA_WIDTH - 1 : 0] id_in_instr;
	wire id_out_enable;
	wire[`OPERATION_TYPE_WIDTH - 1 : 0] id_out_op_type;
	wire[`OPERATION_SUBTYPE_WIDTH - 1 : 0] id_out_op_subtype;
	wire id_out_reg1_read_enable, id_out_reg2_read_enable, id_out_reg_write_enable;
	wire id_out_reg1_read_hi_enable, id_out_reg1_read_lo_enable;
	wire[`REGISTER_ADDRESS_WIDTH - 1 : 0] id_out_reg1_read_addr, id_out_reg2_read_addr, id_out_reg_write_addr;
	wire id_out_reg_write_hi_enable, id_out_reg_write_lo_enable;
	wire id_out_imm_enable;
	wire[`DATA_WIDTH - 1 : 0] id_out_imm_value;
	
	reg tmp_id_out_enable;
	reg[`OPERATION_TYPE_WIDTH - 1 : 0] tmp_op_type;
	reg[`OPERATION_SUBTYPE_WIDTH - 1 : 0] tmp_op_subtype;
	reg tmp_reg1_read_enable, tmp_reg2_read_enable, tmp_reg_write_enable;
	reg tmp_reg1_read_hi_enable, tmp_reg1_read_lo_enable;
	reg[`REGISTER_ADDRESS_WIDTH - 1 : 0] tmp_reg1_read_addr, tmp_reg2_read_addr, tmp_reg_write_addr;
	reg tmp_reg_write_hi_enable, tmp_reg_write_lo_enable;
	reg tmp_imm_enable;
	reg[`DATA_WIDTH - 1 : 0] tmp_imm_value;
	
	wire[`EXTENDED_REGISTER_ADDRESS_WIDTH - 1 : 0] tmp_ext_reg1_read_addr, tmp_ext_reg2_read_addr;
	assign reg_read1_addr =
		(tmp_reg1_read_enable ? tmp_reg1_read_addr : `MIPS_CPU_REGISTER_ZERO_NO);
	assign reg_read2_addr =
		(tmp_reg2_read_enable ? tmp_reg2_read_addr : `MIPS_CPU_REGISTER_ZERO_NO);
	assign tmp_ext_reg1_read_addr =
		(tmp_reg1_read_hi_enable ? `MIPS_CPU_EXTENDED_REGISTER_HI_NO :
		 (tmp_reg1_read_lo_enable ? `MIPS_CPU_EXTENDED_REGISTER_LO_NO : {1'b0, tmp_reg1_read_addr}));
	assign tmp_ext_reg2_read_addr = {1'b0, tmp_reg2_read_addr};
	
	InstructionDecode instruction_decode_i
	(
		.id_enable(id_enable),
		.instr(id_in_instr),
		.id_out_enable(id_out_enable),
		.op_type(id_out_op_type),
		.op_subtype(id_out_op_subtype),
		.reg1_read_enable(id_out_reg1_read_enable),
		.reg1_read_addr(id_out_reg1_read_addr),
		.reg1_read_hi_enable(id_out_reg1_read_hi_enable),
		.reg1_read_lo_enable(id_out_reg1_read_lo_enable),
		.reg2_read_enable(id_out_reg2_read_enable),
		.reg2_read_addr(id_out_reg2_read_addr),
		.reg_write_enable(id_out_reg_write_enable),
		.reg_write_addr(id_out_reg_write_addr),
		.reg_write_hi_enable(id_out_reg_write_hi_enable),
		.reg_write_lo_enable(id_out_reg_write_lo_enable),
		.imm_enable(id_out_imm_enable),
		.imm_value(id_out_imm_value)
	);
	
	reg ex_enable;
	reg[`OPERATION_TYPE_WIDTH - 1 : 0] ex_in_op_type;
	reg[`OPERATION_SUBTYPE_WIDTH - 1 : 0] ex_in_op_subtype;
	reg[`DATA_WIDTH - 1 : 0] ex_in_value1, ex_in_value2, ex_in_value3, ex_in_current_pc_value;
	reg ex_in_reg_write_enable, ex_in_reg_write_hi_enable, ex_in_reg_write_lo_enable;
	reg[`REGISTER_ADDRESS_WIDTH - 1 : 0] ex_in_reg_write_addr;
	reg ex_in_jump_reg;
	wire ex_out_enable;
	wire ex_out_reg_write_enable, ex_out_reg_write_hi_enable, ex_out_reg_write_lo_enable;
	wire[`REGISTER_ADDRESS_WIDTH - 1 : 0] ex_out_reg_write_addr;
	wire[`DATA_WIDTH - 1 : 0] ex_out_reg_written_value;
	
	Execute execute_i
	(
		.ex_enable(ex_enable),
		.op_type(ex_in_op_type),
		.op_subtype(ex_in_op_subtype),
		.value1(ex_in_value1),
		.value2(ex_in_value2),
		.value3(ex_in_value3),
		.current_pc_value(ex_in_current_pc_value),
		.jump_reg(ex_in_jump_reg),
		.reg_write_enable(ex_in_reg_write_enable),
		.reg_write_addr(ex_in_reg_write_addr),
		.reg_write_hi_enable(ex_in_reg_write_hi_enable),
		.reg_write_lo_enable(ex_in_reg_write_lo_enable),
		.ex_out_enable(ex_out_enable),
		.out_jump_enable(jump_enable),
		.out_new_pc_value(jump_to_addr),
		.out_reg_write_enable(ex_out_reg_write_enable),
		.out_reg_write_addr(ex_out_reg_write_addr),
		.out_reg_written_value(ex_out_reg_written_value),
		.out_reg_write_hi_enable(ex_out_reg_write_hi_enable),
		.out_reg_write_lo_enable(ex_out_reg_write_lo_enable)
	);
	
	reg mem_enable, mem_in_reg_write_enable;
	reg[`REGISTER_ADDRESS_WIDTH - 1 : 0] mem_in_reg_write_addr;
	reg mem_in_reg_write_hi_enable, mem_in_reg_write_lo_enable;
	reg[`DATA_WIDTH - 1 : 0] mem_in_reg_written_value;
	wire mem_out_enable, mem_out_reg_write_enable;
	wire[`REGISTER_ADDRESS_WIDTH - 1 : 0] mem_out_reg_write_addr;
	wire mem_out_reg_write_hi_enable, mem_out_reg_write_lo_enable;
	wire[`DATA_WIDTH - 1 : 0] mem_out_reg_written_value;
	
	MemoryAccess memory_access_i
	(
		.mem_enable(mem_enable),
		.reg_write_enable(mem_in_reg_write_enable),
		.reg_write_addr(mem_in_reg_write_addr),
		.reg_write_hi_enable(mem_in_reg_write_hi_enable),
		.reg_write_lo_enable(mem_in_reg_write_lo_enable),
		.reg_written_value(mem_in_reg_written_value),
		.mem_out_enable(mem_out_enable),
		.out_reg_write_enable(mem_out_reg_write_enable),
		.out_reg_write_addr(mem_out_reg_write_addr),
		.out_reg_write_hi_enable(mem_out_reg_write_hi_enable),
		.out_reg_write_lo_enable(mem_out_reg_write_lo_enable),
		.out_reg_written_value(mem_out_reg_written_value)
	);
	
	reg[`EXTENDED_REGISTER_ADDRESS_WIDTH - 1 : 0] reg_mutex[`REGISTER_MUTEX_LENGTH - 1 : 0];
	wire pipeline_pause;
	assign pipeline_pause =
		(reg_mutex[0] != `MIPS_CPU_EXTENDED_REGISTER_ZERO_NO && 
		 (tmp_reg1_read_enable && reg_mutex[0] == tmp_ext_reg1_read_addr ||
		  tmp_reg2_read_enable && reg_mutex[0] == tmp_ext_reg2_read_addr)) ||
		(reg_mutex[1] != `MIPS_CPU_EXTENDED_REGISTER_ZERO_NO && 
		 (tmp_reg1_read_enable && reg_mutex[1] == tmp_ext_reg1_read_addr ||
		  tmp_reg2_read_enable && reg_mutex[1] == tmp_ext_reg2_read_addr));
	integer i;
							
	always @(negedge rst or negedge clk)
	begin
		if (!rst) begin
			for (i = 0; i < `REGISTER_MUTEX_LENGTH; i = i + 1)
				reg_mutex[i] <= `MIPS_CPU_EXTENDED_REGISTER_ZERO_NO;
			tmp_if_out_enable <= 0;
			tmp_id_out_enable <= 0;
			tmp_op_type <= `OPERATION_TYPE_NOP;
			tmp_op_subtype <= `OPERATION_SUBTYPE_NOP;
			tmp_reg1_read_enable <= 0;
			tmp_reg1_read_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			tmp_reg1_read_hi_enable <= 0;
			tmp_reg1_read_lo_enable <= 0;
			tmp_reg2_read_enable <= 0;
			tmp_reg2_read_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			tmp_imm_enable <= 0;
			tmp_imm_value <= 0;
			tmp_reg_write_enable <= 0;
			tmp_reg_write_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			tmp_reg_write_hi_enable <= 0;
			tmp_reg_write_lo_enable <= 0;
		end else if (!clk) begin
			reg_mutex[0] <= reg_mutex[1];
			if (pipeline_pause)
				reg_mutex[1] <= `MIPS_CPU_EXTENDED_REGISTER_ZERO_NO;
			else
				reg_mutex[1] <= reg_mutex[2];
			if (id_out_reg_write_enable) begin
				if (id_out_reg_write_hi_enable)
					reg_mutex[2] <= `MIPS_CPU_EXTENDED_REGISTER_HI_NO;
				else if (id_out_reg_write_lo_enable)
					reg_mutex[2] <= `MIPS_CPU_EXTENDED_REGISTER_LO_NO;
				else
					reg_mutex[2] <= {1'b0, id_out_reg_write_addr};
			end else
				reg_mutex[2] <= `MIPS_CPU_EXTENDED_REGISTER_ZERO_NO;
			if (if_out_enable)
				tmp_if_out_enable <= if_out_enable;
			if (id_out_enable) begin
				tmp_id_out_enable <= id_out_enable;
				tmp_op_type <= id_out_op_type;
				tmp_op_subtype <= id_out_op_subtype;
				tmp_reg1_read_enable <= id_out_reg1_read_enable;
				tmp_reg1_read_addr <= id_out_reg1_read_addr;
				tmp_reg1_read_hi_enable <= id_out_reg1_read_hi_enable;
				tmp_reg1_read_lo_enable <= id_out_reg1_read_lo_enable;
				tmp_reg2_read_enable <= id_out_reg2_read_enable;
				tmp_reg2_read_addr <= id_out_reg2_read_addr;
				tmp_imm_enable <= id_out_imm_enable;
				tmp_imm_value <= id_out_imm_value;
				tmp_reg_write_enable <= id_out_reg_write_enable;
				tmp_reg_write_addr <= id_out_reg_write_addr;
				tmp_reg_write_hi_enable <= id_out_reg_write_hi_enable;
				tmp_reg_write_lo_enable <= id_out_reg_write_lo_enable;
			end
		end
	end
	
	always @(negedge rst or posedge clk)
	begin
		if (!rst) begin
			reg_write_enable <= 0;
			reg_write_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			reg_written_value <= 0;
			pc_write_enable <= 0;
			hi_write_enable <= 0;
			lo_write_enable <= 0;
			pc_written_value <= 0;
			hi_written_value <= 0;
			lo_written_value <= 0;
			if_enable <= 0;
			id_enable <= 0;
			id_in_instr <= 0;
			ex_enable <= 0;
			ex_in_op_type <= `OPERATION_TYPE_NOP;
			ex_in_op_subtype <= `OPERATION_SUBTYPE_NOP;
			ex_in_value1 <= 0;
			ex_in_value2 <= 0;
			ex_in_value3 <= 0;
			ex_in_current_pc_value <= 0;
			ex_in_reg_write_enable <= 0;
			ex_in_reg_write_hi_enable <= 0;
			ex_in_reg_write_lo_enable <= 0;
			ex_in_reg_write_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			ex_in_jump_reg <= 0;
			mem_enable <= 0;
			mem_in_reg_write_enable <= 0;
			mem_in_reg_write_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			mem_in_reg_written_value <= 0;
		end else if (clk) begin
			if (cpu_enable && !pipeline_pause)
				if_enable <= 1;
			else
				if_enable <= 0;
			
			if (tmp_if_out_enable && !pipeline_pause) begin
				id_enable <= 1;
				/*if (jump_enable)
					id_in_instr <= `INSTRUCTION_NOP;
				else*/
					id_in_instr <= instr;
				pc_write_enable <= 1;
				pc_written_value <= out_pc_value;
			end else begin
				id_enable <= 0;
				pc_write_enable <= 0;
			end
			
			if (tmp_id_out_enable && !pipeline_pause) begin
				ex_enable <= 1;
				ex_in_current_pc_value <= pc_value;
				/*if (jump_enable) begin
					ex_in_op_type <= `OPERATION_TYPE_NOP;
					ex_in_op_subtype <= `OPERATION_SUBTYPE_NOP;
					ex_in_value1 <= 0;
					ex_in_value2 <= 0;
					ex_in_value3 <= 0;
					ex_in_jump_reg <= 0;
					ex_in_reg_write_enable <= 0;
					ex_in_reg_write_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
					ex_in_reg_write_hi_enable <= 0;
					ex_in_reg_write_lo_enable <= 0;
				end else begin*/
					ex_in_op_type <= tmp_op_type;
					ex_in_op_subtype <= tmp_op_subtype;
					if (tmp_op_type == `OPERATION_TYPE_JUMP && tmp_reg1_read_enable) begin
						ex_in_jump_reg <= 1;
						ex_in_value1 <= 0;
						ex_in_value2 <= 0;
						ex_in_value3 <= reg_read1_value;
					end else begin
						ex_in_jump_reg <= 0;
						if (tmp_reg1_read_enable) begin
							if (tmp_reg1_read_hi_enable)
								ex_in_value1 <= hi_value;
							else if (tmp_reg1_read_lo_enable)
								ex_in_value1 <= lo_value;
							else
								ex_in_value1 <= reg_read1_value;
						end else if (tmp_op_subtype == `OPERATION_SUBTYPE_GET && tmp_imm_enable)
							ex_in_value1 <= tmp_imm_value;
						else
							ex_in_value1 <= 0;
						if (tmp_reg2_read_enable)
							ex_in_value2 <= reg_read2_value;
						else if (tmp_op_type == `OPERATION_TYPE_ALU && tmp_imm_enable)
							ex_in_value2 <= tmp_imm_value;
						else
							ex_in_value2 <= 0;
						if ((tmp_op_type == `OPERATION_TYPE_BRANCH
							|| tmp_op_type == `OPERATION_TYPE_JUMP)
							&& tmp_imm_enable)
							ex_in_value3 <= tmp_imm_value;
						else
							ex_in_value3 <= 0;
					end
					ex_in_reg_write_enable <= tmp_reg_write_enable;
					ex_in_reg_write_addr <= tmp_reg_write_addr;
					ex_in_reg_write_hi_enable <= tmp_reg_write_hi_enable;
					ex_in_reg_write_lo_enable <= tmp_reg_write_lo_enable;
				//end
			end else begin
				ex_enable <= 0;
				ex_in_reg_write_enable <= 0;
				ex_in_reg_write_hi_enable <= 0;
				ex_in_reg_write_lo_enable <= 0;
			end
			
			if (ex_out_enable) begin
				mem_enable <= 1;
				mem_in_reg_write_enable <= ex_out_reg_write_enable;
				mem_in_reg_write_addr <= ex_out_reg_write_addr;
				mem_in_reg_write_hi_enable <= ex_out_reg_write_hi_enable;
				mem_in_reg_write_lo_enable <= ex_out_reg_write_lo_enable;
				mem_in_reg_written_value <= ex_out_reg_written_value;
			end else begin
				mem_enable <= 0;
				mem_in_reg_write_enable <= 0;
				mem_in_reg_write_hi_enable <= 0;
				mem_in_reg_write_lo_enable <= 0;
			end
			
			if (mem_out_enable && mem_out_reg_write_enable) begin
				if (mem_out_reg_write_hi_enable) begin
					hi_write_enable <= 1;
					hi_written_value <= mem_out_reg_written_value;
					lo_write_enable <= 0;
					reg_write_enable <= 0;
				end else if (mem_out_reg_write_lo_enable) begin
					hi_write_enable <= 0;
					lo_write_enable <= 1;
					lo_written_value <= mem_out_reg_written_value;
					reg_write_enable <= 0;
				end else begin
					hi_write_enable <= 0;
					lo_write_enable <= 0;
					reg_write_enable <= 1;
					reg_write_addr <= mem_out_reg_write_addr;
					reg_written_value <= mem_out_reg_written_value;
				end
			end else begin
				hi_write_enable <= 0;
				lo_write_enable <= 0;
				reg_write_enable <= 0;
			end
		
		end
	end
	
endmodule
